from heap.class_klass import HeapObject, Klass
from heap.system_ref import FieldRef
from insts.inst import Index16Instruction

from jthread.frame import JFrame
from jvm.base_vm import BaseVM


class PUT_FIELD(Index16Instruction):
    def execute(self, frame: JFrame):
        method = frame.getJMethod()
        klass = method.getKlass()
        cp = klass.getConstantPool()
        fieldRef: FieldRef = cp.getFieldRef(self.index)
        # TODO: resolve field 存在疑问
        field = fieldRef.resolveField()
        if field.isStatic():
            raise Exception("Static field can not be set")
        if field.isFinal():
            if klass != field.getKlass() or method.getName() != "<init>":
                raise Exception("Final field can only be set in constructor or instance initializer")
        descriptor = field.getDescriptor()
        slotId = field.getSlotId()
        stack = frame.getOperandStack()

        value = None
        if descriptor == "Z" or descriptor == "C" or descriptor == "B" or descriptor == "S" or descriptor == "I":
            value = stack.popInt()
        elif descriptor == "J":
            value = stack.popLong()
        elif descriptor == "F":
            value = stack.popFloat()
        elif descriptor == "D":
            value = stack.popDouble()
        elif descriptor[0] == "L" or descriptor[0] == "[":
            value = stack.popRef()
        else:
            raise Exception("Unsupported field type")

        ref: HeapObject = stack.popRef()
        if ref is None:
            raise Exception("NullPointerException")
        ref.Fields().setInt(slotId, value)


class GET_FIELD(Index16Instruction):
    def execute(self, frame: JFrame):
        method = frame.getJMethod()
        klass = method.getKlass()
        cp = klass.getConstantPool()
        fieldRef: FieldRef = cp.getFieldRef(self.index)
        # TODO: resolve field 存在疑问
        field = fieldRef.resolveField()
        if field.isStatic():
            raise Exception("Static field can not be get")
        stack = frame.getOperandStack()
        ref: HeapObject = stack.popRef()
        if ref is None:
            raise Exception("NullPointerException")
        descriptor = field.getDescriptor()
        slotId = field.getSlotId()
        if descriptor == "Z" or descriptor == "C" or descriptor == "B" or descriptor == "S" or descriptor == "I":
            stack.pushInt(ref.Fields().getInt(slotId))
        elif descriptor == "J":
            stack.pushLong(ref.Fields().getLong(slotId))
        elif descriptor == "F":
            stack.pushFloat(ref.Fields().getFloat(slotId))
        elif descriptor == "D":
            stack.pushDouble(ref.Fields().getDouble(slotId))
        elif descriptor[0] == "L" or descriptor[0] == "[":
            stack.pushRef(ref.Fields().getRef(slotId))
        else:
            raise Exception("Unsupported field type")


class PUT_STATIC_FIELD(Index16Instruction):
    def execute(self, frame: JFrame):
        method = frame.getJMethod()
        klass:Klass = method.getKlass()
        cp = klass.getConstantPool()
        fieldRef: FieldRef = cp.getFieldRef(self.index)
        # TODO: resolve field 存在疑问
        field = fieldRef.resolveField()

        if not field.getKlass().isinit():
            frame.revertNextPc()
            BaseVM().initClass(frame.getJThread(), field.getKlass())
            return
        if not field.isStatic():
            raise Exception("Static field can not be set")
        if field.isFinal():
            if klass != field.getKlass() or method.getName() != "<init>":
                raise Exception("Final field can only be set in constructor or instance initializer")
        descriptor = field.getDescriptor()
        slotId = field.getSlotId()
        stack = frame.getOperandStack()
        if descriptor == "Z" or descriptor == "C" or descriptor == "B" or descriptor == "S" or descriptor == "I":
            value = stack.popInt()
        elif descriptor == "J":
            value = stack.popLong()
        elif descriptor == "F":
            value = stack.popFloat()
        elif descriptor == "D":
            value = stack.popDouble()
        elif descriptor[0] == "L" or descriptor[0] == "[":
            value = stack.popRef()
        else:
            raise Exception("Unsupported field type")

        staticFields = klass.getStaicFields()
        if staticFields is None:
            raise Exception("NullPointerException")
        if descriptor == "Z" or descriptor == "C" or descriptor == "B" or descriptor == "S" or descriptor == "I":
            staticFields.setInt(slotId, value)
        elif descriptor == "J":
            staticFields.setLong(slotId, value)
        elif descriptor == "F":
            staticFields.setFloat(slotId, value)
        elif descriptor == "D":
            staticFields.setDouble(slotId, value)
        elif descriptor[0] == "L" or descriptor[0] == "[":
            staticFields.setRef(slotId, value)
        else:
            raise Exception("Unsupported field type")


class GET_STATIC_FIELD(Index16Instruction):
    def execute(self, frame: JFrame):
        method = frame.getJMethod()
        klass = method.getKlass()
        cp = klass.getConstantPool()
        fieldRef: FieldRef = cp.getFieldRef(self.index)
        # TODO: resolve field 存在疑问
        field = fieldRef.resolveField()
        if not field.isStatic():
            raise Exception("Static field can not be get")
        stack = frame.getOperandStack()
        staticFields = klass.getStaicFields()
        if staticFields is None:
            raise Exception("NullPointerException")
        descriptor = field.getDescriptor()
        slotId = field.getSlotId()
        if descriptor == "Z" or descriptor == "C" or descriptor == "B" or descriptor == "S" or descriptor == "I":
            stack.pushInt(staticFields.getInt(slotId))
        elif descriptor == "J":
            stack.pushLong(staticFields.getLong(slotId))
        elif descriptor == "F":
            stack.pushFloat(staticFields.getFloat(slotId))
        elif descriptor == "D":
            stack.pushDouble(staticFields.getDouble(slotId))
        elif descriptor[0] == "L" or descriptor[0] == "[":
            stack.pushRef(staticFields.getRef(slotId))
        else:
            raise Exception("Unsupported field type")


